#include "stdafx.h"
#include "MeshGeometry.h"
#include "Material.h"
#include "RayTraceSystem.h"
#include "Debug.h"
#include "ObjModel.h"
#include "kernels_cpu.h"
#include <ppl.h>
using namespace GxLibBasic;

namespace RayTracePro
{
	const int MeshKdTreeMaxDepth = 32;
	using namespace Concurrency;

	class ModelFileNotSupportedException : public Exception
	{
	public:
		ModelFileNotSupportedException(const String & fileName)
			: Exception(String(L"Can't load model \'") + fileName + L"\', no loader found for the specified model type", __FILE__, __LINE__)
		{
		}
	};

	RefPtr<Mapping<String, GeometryLoaderFunction>> GeometryLoader::loaders;

	class MeshKdTreeEvaluator
	{
	public:
		static const float IntersectTime;
		static const float TraversalTime;
		static const float EmptyNodeBounus;
		static const int ElementsPerNode = 1;
	};

	const float MeshKdTreeEvaluator::IntersectTime = 80.0f;
	const float MeshKdTreeEvaluator::TraversalTime = 1.0f;
	const float MeshKdTreeEvaluator::EmptyNodeBounus = 0.4f;

	// Trace Triangle
	template<bool pred>
	class MeshTracer
	{
	public:
		MeshGeometry * mesh;
		RayTraceSystem * system;
		inline bool Trace(DifferentialGeometry & inter, const MeshFace & face, int faceId, const Ray & ray, const RayDifferential & diffIn, float & t) const
		{
			//MeshTracingTimer(pred?2:1);
			float invNdotD = 1.0f / (face.PlaneX * ray.Direction.x + face.PlaneY * ray.Direction.y + face.PlaneZ * ray.Direction.z);
			float tplane = -(face.PlaneX * ray.Origin.x + face.PlaneY * ray.Origin.y + face.PlaneZ * ray.Origin.z + face.PlaneW) * invNdotD;
			if (tplane < system->GeometryBias/*-Epsilon*/ || tplane > ray.tMax/*+Epsilon*/)
				return false;
			Vec3 hit;
			Vec3::Scale(hit, ray.Direction, tplane);
			hit += ray.Origin;

			float beta = face.BetaX * hit.x + face.BetaY * hit.y + face.BetaZ * hit.z + face.BetaW;
			if (beta < -Epsilon)
				return false;
			float gamma = face.GammaX * hit.x + face.GammaY * hit.y + face.GammaZ * hit.z + face.GammaW;
			if (gamma < -Epsilon)
				return false;
			if (beta + gamma > 1.0f + Epsilon)
				return false;
			if (pred)
				return true;
			inter.Position = hit;
			float alpha = 1.0f - beta - gamma;
			Vec3 tmp;
			Vec3::Scale(inter.Normal, mesh->Normals[face.Normal1], alpha);
			Vec3::Scale(tmp, mesh->Normals[face.Normal2], beta);
			inter.Normal += tmp;
			Vec3::Scale(tmp, mesh->Normals[face.Normal3], gamma);
			inter.Normal += tmp;

			Vec2 tmpVec2;
			if (face.TexCoord1 != -1)
			{
				inter.UV.x = mesh->TexCoords[face.TexCoord1].x * alpha;
				inter.UV.y = mesh->TexCoords[face.TexCoord1].y * alpha;
				inter.UV.x += mesh->TexCoords[face.TexCoord2].x * beta;
				inter.UV.y += mesh->TexCoords[face.TexCoord2].y * beta;
				inter.UV.x += mesh->TexCoords[face.TexCoord3].x * gamma;
				inter.UV.y += mesh->TexCoords[face.TexCoord3].y * gamma;
			}
			inter.dNdxContinuation = faceId;
			t = tplane;
			inter.MaterialId = face.MaterialId;
			return true;
		}
	};

	cl::Kernel MeshGeometry::traceMeshKernel, MeshGeometry::traceMeshPacketKernel, MeshGeometry::fillIntersectionKernel;
	bool MeshGeometry::kernelCreated = false;

	MeshGeometry::MeshGeometry()
	{
		this->AutoFlipNormals = false;
		this->dataInDevice = false;
		this->Bounds.Init();
		if (!kernelCreated)
		{
			kernelCreated = true;
			traceMeshKernel = GpuDevice::CreateKernel("TransformTraceMesh");
			fillIntersectionKernel = GpuDevice::CreateKernel("FillIntersection");
			//traceMeshPacketKernel = GpuDevice::CreateKernel("RayMeshIntersectionPacket");
		}
	}

	void MeshGeometry::SetFace(int i, const Vec3 verts[3], int norm[3], int tex[3], short materialId)
	{
		BBox box;
		box.Max = box.Min = verts[0];
		box.Union(verts[1]);
		box.Union(verts[2]);
		faceBBoxes[i] = box;
		//Bounds.Union(box);
		MeshFace f;
		f.Normal1 = norm[0]; f.Normal2 = norm[1]; f.Normal3 = norm[2];
		f.TexCoord1 = tex[0]; f.TexCoord2 = tex[1]; f.TexCoord3 = tex[2];
		Vec3 n, c, b, a;
		Vec3::Subtract(c, verts[1], verts[0]);
		Vec3::Subtract(b, verts[2], verts[0]);
		Vec3::Subtract(a, verts[2], verts[1]);
		Vec3::Cross(n, c, b);
		Vec3::Normalize(n, n);
		f.PlaneX = n.x; f.PlaneY = n.y; f.PlaneZ = n.z;
		f.PlaneW = -Vec3::Dot(n, verts[0]); 
		Vec3 pn;
		Vec3::Cross(pn, n, a);
		f.AlphaX = pn.x; f.AlphaY = pn.y; f.AlphaZ = pn.z; f.AlphaW = -Vec3::Dot(pn, verts[1]);
		float divisor = 1.0f / (Vec3::Dot(pn, verts[0]) + f.AlphaW);
		f.AlphaX *= divisor; f.AlphaY *= divisor; f.AlphaZ *= divisor; f.AlphaW *= divisor;

		Vec3::Cross(pn, n, b);
		f.BetaX = pn.x; f.BetaY = pn.y; f.BetaZ = pn.z; f.BetaW = -Vec3::Dot(pn, verts[2]);
		divisor = 1.0f / (Vec3::Dot(pn, verts[1]) + f.BetaW);
		f.BetaX *= divisor; f.BetaY *= divisor; f.BetaZ *= divisor; f.BetaW *= divisor;

		Vec3::Cross(pn, n, c);
		f.GammaX = pn.x; f.GammaY = pn.y; f.GammaZ = pn.z; f.GammaW = -Vec3::Dot(pn, verts[0]);
		divisor = 1.0f / (Vec3::Dot(pn, verts[2]) + f.GammaW);
		f.GammaX *= divisor; f.GammaY *= divisor; f.GammaZ *= divisor; f.GammaW *= divisor;

		f.MaterialId = materialId;
		f.Padding = f.TexCoord1;
		Faces[i] = f;
	}

	void MeshGeometry::UpdateAccelerateStructure()
	{
		BBoxEdge * edges = new BBoxEdge[Faces.Count()*2];
		int * geomLeft = new int[Faces.Count()];
		int * geomRight = new int[Faces.Count()*MeshKdTreeMaxDepth];
		memset(geomLeft, 0, sizeof(int)*Faces.Count());
		memset(geomRight, 0, sizeof(int)*Faces.Count()*MeshKdTreeMaxDepth);
		for (int i = 0; i<Faces.Count(); i++)
		{
			geomLeft[i] = i;
		}
		ConstructKdTree<MeshFace, MeshKdTreeEvaluator>(tree, Faces.Buffer(), 0, faceBBoxes, 
			edges, geomLeft, Faces.Count(), geomLeft, geomRight, MeshKdTreeMaxDepth, 0);
		delete [] geomLeft;
		delete [] geomRight;
		delete [] edges;
		faceBBoxes.SetSize(0);
		faceBBoxes.Compress();
	}

	void MeshGeometry::Compute_dNdx(DifferentialGeometry & inter, const Vec3 & dPdx, const Vec3 & dPdy)
	{
		const int mod3[] = {0,1,2,0,1};
		MeshFace * face = (MeshFace*)(inter.dNdxContinuation);
		MeshGeometry * mesh = this;
		Vec3 dndx, dndy;
		Vec3 tmp, tmp2;
		float beta = face->BetaX * dPdx.x + face->BetaY * dPdx.y + face->BetaZ * dPdx.z;
		float gamma = face->GammaX * dPdx.x + face->GammaY * dPdx.y + face->GammaZ * dPdx.z;
		float alpha = face->AlphaX * dPdx.x + face->AlphaY * dPdx.y + face->AlphaZ * dPdx.z;
		Vec3::Scale(dndx, mesh->Normals[face->Normal1], alpha);
		Vec3::Scale(tmp, mesh->Normals[face->Normal2], beta);
		dndx += tmp;
		Vec3::Scale(tmp, mesh->Normals[face->Normal3], gamma);
		dndx += tmp;
		float nDotn = Vec3::Dot(inter.Normal, inter.Normal);
		float divisor = 1.0f / sqrt(nDotn * nDotn * nDotn);
		Vec3::Scale(tmp, dndx, nDotn);
		Vec3::Scale(tmp2, inter.Normal, Vec3::Dot(inter.Normal, dndx));
		tmp -= tmp2;
		Vec3::Scale(inter.dNdx, tmp, divisor);
		if (face->TexCoord1 != -1)
		{
			inter.dUdx = mesh->TexCoords[face->TexCoord1].x * alpha;
			inter.dVdx = mesh->TexCoords[face->TexCoord1].y * alpha;
			inter.dUdx += mesh->TexCoords[face->TexCoord2].x * beta;
			inter.dVdx += mesh->TexCoords[face->TexCoord2].y * beta;
			inter.dUdx += mesh->TexCoords[face->TexCoord3].x * gamma;
			inter.dVdx += mesh->TexCoords[face->TexCoord3].y * gamma;
		}
		beta = face->BetaX * dPdy.x + face->BetaY * dPdy.y + face->BetaZ * dPdy.z;
		gamma = face->GammaX * dPdy.x + face->GammaY * dPdy.y + face->GammaZ * dPdy.z;
		alpha = face->AlphaX * dPdy.x + face->AlphaY * dPdy.y + face->AlphaZ * dPdy.z;
		Vec3::Scale(dndy, mesh->Normals[face->Normal1], alpha);
		Vec3::Scale(tmp, mesh->Normals[face->Normal2], beta);
		dndy += tmp;
		Vec3::Scale(tmp, mesh->Normals[face->Normal3], gamma);
		dndy += tmp;
		Vec3::Scale(tmp, dndy, nDotn);
		Vec3::Scale(tmp2, inter.Normal, Vec3::Dot(inter.Normal, dndy));
		tmp -= tmp2;
		Vec3::Scale(inter.dNdy, tmp, divisor);
		if (face->TexCoord1 != -1)
		{
			inter.dUdy = mesh->TexCoords[face->TexCoord1].x * alpha;
			inter.dVdy = mesh->TexCoords[face->TexCoord1].y * alpha;
			inter.dUdy += mesh->TexCoords[face->TexCoord2].x * beta;
			inter.dVdy += mesh->TexCoords[face->TexCoord2].y * beta;
			inter.dUdy += mesh->TexCoords[face->TexCoord3].x * gamma;
			inter.dVdy += mesh->TexCoords[face->TexCoord3].y * gamma;
		}
		inter.dNdxContinuation = -1;
	}

//#define GPU_SIMULATION

	void MeshGeometry::ComputeIntersectionGPU ( RayTraceSystem * system, 
												int start, 
												int end, 
												DeviceList<int> & rayIds, 
												DeviceList<TraverseResult> & continuation, // indicates the instance of each ray
												DeviceList<GeometryInstance> & instances, // transform matrices of each instance
												DeviceList<Ray> & rays, 
												DeviceList<RayDifferential> & rayDiffIns, 
												DeviceList<Vec4> & intermediateIntersections,
												DeviceList<DifferentialGeometry> & inters // result
											  )
	{
		int size = rays.Count();
		int blockSize = 128;
		while (blockSize > 64 && size < 512*blockSize) blockSize >>= 1;
		int alignedSize = Align(size, blockSize);
		auto evt = GpuDevice::InvokeKernel(traceMeshKernel, alignedSize, blockSize)
			(start, 
			end, 
			size, 
			continuation.DeviceBuffer(false), 
			rayIds.DeviceBuffer(false), 
			instances.DeviceBuffer(false), 
			rays.DeviceBuffer(false), 
			system->GeometryBias, 
			tree.Bounds, (int)AutoFlipNormals, 
			tree.Nodes.DeviceBuffer(!dataInDevice), 
			tree.Elements.DeviceBuffer(!dataInDevice), 
			tree.Leaves.DeviceBuffer(!dataInDevice),
			intermediateIntersections.DeviceBuffer(false));
		//clFlush(GpuDevice::GetQueue()());
		//evt.Wait();
		blockSize = 256;
		while (blockSize > 64 && size < 512*blockSize) blockSize >>= 1;
		alignedSize = Align(size, blockSize);
		evt = GpuDevice::InvokeKernel(fillIntersectionKernel, alignedSize, blockSize)
			(start,
			end,
			size, 
			continuation.DeviceBuffer(false), 
			rayIds.DeviceBuffer(false), 
			instances.DeviceBuffer(false), 
			rays.DeviceBuffer(false), 
			tree.Elements.DeviceBuffer(false), 
			Normals.DeviceBuffer(!dataInDevice),
			TexCoords.DeviceBuffer(!dataInDevice),
			intermediateIntersections.DeviceBuffer(false), 
			inters.DeviceBuffer(false));
		//evt.Wait();
		dataInDevice = true;
	}
	
	bool MeshGeometry::ComputeIntersection(RayTraceSystem * system, int rootNode, DifferentialGeometry & inter, 
		const Ray & ray, const RayDifferential & rayDiffIn, float & t)
	{
		MeshTracer<false> tracer;
		tracer.mesh = this;
		tracer.system = system;
		bool rs = TraverseKdTreeStackless<MeshFace, MeshTracer<false>, false, false>(system, (KdNode *)((char*)tree.Nodes.Buffer() + rootNode), tracer, inter, tree, ray, rayDiffIn, t);
		if (rs && this->AutoFlipNormals && Vec3::Dot(inter.Normal, ray.Direction) > 0.0f)
		{
			inter.Normal.x = -inter.Normal.x;
			inter.Normal.y = -inter.Normal.y;
			inter.Normal.z = -inter.Normal.z;
		}
		return rs;
	}

	bool MeshGeometry::ComputeIntersectionPred(RayTraceSystem * system, const Ray & ray)
	{
		MeshTracer<true> tracer;
		tracer.mesh = this;
		tracer.system = system;
		DifferentialGeometry inter;
		RayDifferential rayDiffIn;
		float t;
		return TraverseKdTree<MeshFace, MeshTracer<true>, true, false>(system, tracer, inter, tree, ray, rayDiffIn, t);
	}

	struct MeshCreateLeafBoundingSphereFunction
	{
		int bufferSize;
		Array<Vec4, true> & buffer;
		MeshCreateLeafBoundingSphereFunction(Array<Vec4, true> & _buffer)
			:buffer(_buffer)
		{}
		void operator()(BBox & curBox, int nodeId, int depth)
		{
			Vec3 position, range;
			Vec4 sphere;
			Vec3::Add(position, curBox.Min, curBox.Max);
			Vec3::Scale(position, position, 0.5f);
			sphere.x = position.x; sphere.y = position.y; sphere.z = position.z;
			Vec3::Subtract(range, curBox.Max, curBox.Min);
			sphere.w = range.Length() * 0.5f;
			buffer.Add(sphere);
		}
	};

	void MeshGeometry::ComputeBoundingSpheres(int depth, Array<Vec4, true> & buffer)
	{
		MeshCreateLeafBoundingSphereFunction cleaf(buffer);
		ComputeKdTreeBoundingSpheres(tree, depth, buffer, cleaf);
	}

	void MeshGeometry::UpdateBounds()
	{
		Bounds.Init();
		BBox identity;
		identity.Init();
		Bounds = parallel_reduce(faceBBoxes.Buffer(), faceBBoxes.Buffer()+faceBBoxes.Count(), identity, [&](const BBox & b1, const BBox &b2)
		{
			return BBox(b1,b2);
		});
	}

	bool LoadObjModel(RayTraceSystem * system, MeshGeometry & geometry, const char * fileName)
	{
		ObjModel mdl;
		DebugTimming timer;
		timer.Start("LoadMesh(obj)");
		timer.Start("ReadFile");
		if (!LoadObj(mdl, fileName)) return false;
		timer.End();
		timer.Start("Processing");
		for (int i = 0; i<mdl.Materials.Count(); i++)
		{
			Material * mat = system->CreateMaterial();
			geometry.Materials.Add(mat);
			/*mat->Shader = system->CreateShader(L"Lambert");
			Vec4 diffuse(mdl.Materials[i]->Diffuse, 1.0f);
			if (mdl.Materials[i]->DiffuseMap.GetLength())
			{
				String basePath = ExtractFilePath(fileName);
				String diffuseMap = basePath + mdl.Materials[i]->DiffuseMap;
				ShadingNode * texture = system->CreateShader(L"Texture");
				texture->SetParameter(L"File Name", diffuseMap.GetStrPtr());
				texture->SetParameter(L"Multiplier", diffuse);
				mat->Shader->SetParameter(L"Diffuse", texture);
				
				mat->GI.Diffuse.Set(texture);
				texture->AddRef();
			}
			else
			{
				mat->Shader->SetParameter(L"Diffuse", diffuse);
				mat->GI.Diffuse.Set(diffuse);
			}
			mat->GI.Lambert = true;
			*/
		}
		RecomputeNormals(mdl);
		geometry.Normals.Add(mdl.Normals);
		geometry.TexCoords.Add(mdl.TexCoords);
		geometry.SetFaceCount(mdl.Faces.Count());
		parallel_for(0, mdl.Faces.Count(), [&](int i)
		{
			Vec3 verts[3];
			ObjFace & face = mdl.Faces[i];
			verts[0] = mdl.Vertices[face.VertexIds[0]];
			verts[1] = mdl.Vertices[face.VertexIds[1]];
			verts[2] = mdl.Vertices[face.VertexIds[2]];
			geometry.SetFace(i, verts, face.NormalIds, face.TexCoordIds, face.MaterialId);
		});
		geometry.UpdateBounds();
		timer.End();
		timer.End();
		return true;
	}
	struct FaceInFile
	{
		Vec3 Vertices[3];
		ObjFace Face;
	};
	void CreateMengerSponge(int xi, int yi, int zi, double l, int m, int level, int maxLevel, ObjModel * mdl);
	bool LoadSponge(MeshGeometry & geometry, int level)
	{
		ObjModel mdl;
		if (level == 5)
		{
			mdl.Vertices.SetCapacity(13589913*3);
			mdl.Faces.SetCapacity(13589913);
		}
		CreateMengerSponge(0, 0, 0, 1.0f, 0x3F, 0, level, &mdl);
		RecomputeNormals(mdl);
		geometry.Normals.Add(mdl.Normals);
		geometry.TexCoords.Add(mdl.TexCoords);
		Vec3 verts[3];
		int faceCount = mdl.Faces.Count();
		geometry.SetFaceCount(faceCount);
		FILE * f;
		if (level == 5)
		{
			printf("Dumping to disk...\n");	
			fopen_s(&f, "d:\\sponge_dump.tmp", "wb");
			int countBuf = mdl.Faces.Count();
			for (int i = 0; i<countBuf; i++)
			{
				FaceInFile ff;
				ff.Face = mdl.Faces[i];
				ff.Vertices[0] = mdl.Vertices[mdl.Faces[i].VertexIds[0]];
				ff.Vertices[1] = mdl.Vertices[mdl.Faces[i].VertexIds[1]];
				ff.Vertices[2] = mdl.Vertices[mdl.Faces[i].VertexIds[2]];
				fwrite(&ff, sizeof(FaceInFile), 1, f);
			}
			fclose(f);
			fopen_s(&f, "d:\\sponge_dump.tmp", "rb");
			mdl.Faces.SetSize(0);
			mdl.Faces.Compress();
			mdl.Vertices.SetSize(0);
			mdl.Vertices.Compress();
			//geometry.ReserveFaceBuffer(faceCount);
		}
		for (int i = 0; i < faceCount; i++)
		{
			ObjFace face;
			if (level == 5)
			{
				FaceInFile ff;
				fread(&ff, sizeof(FaceInFile), 1, f);
				face = ff.Face;
				verts[0] = ff.Vertices[0];
				verts[1] = ff.Vertices[1];
				verts[2] = ff.Vertices[2];
			}
			else
			{
				face = mdl.Faces[i];
				verts[0] = mdl.Vertices[face.VertexIds[0]];
				verts[1] = mdl.Vertices[face.VertexIds[1]];
				verts[2] = mdl.Vertices[face.VertexIds[2]];
			}
			geometry.SetFace(i, verts, face.NormalIds, face.TexCoordIds);
		}
		if (level == 5)
		{
			fclose(f);
		}
		printf("menger sponge(%d): v:%d, f:%d\n", level, mdl.Vertices.Count(), mdl.Faces.Count());
		return true;
	}
	void GeometryLoader::Init()
	{
		loaders = new Mapping<String, GeometryLoaderFunction>();
		loaders->Add(L"obj", LoadObjModel);
	}

	bool GeometryLoader::LoadGeometryFromFile(RayTraceSystem * system, MeshGeometry & geometry, const String & fileName)
	{
		GeometryLoaderFunction func;
		if (loaders->Lookup(fileName.Right(3).Lowercase(), func))
		{
			return func(system, geometry, fileName.ToMBString());
		}
		else
		{
			throw ModelFileNotSupportedException(fileName);
		}
		return false;
	}

	void CreateMengerSponge(int xi, int yi, int zi, double l, int m, int level, int maxLevel, ObjModel * mdl)
	{
		static const Vec3 msVertices[8]=
		{
			Vec3(0.0f, 1.0f, 1.0f),
			Vec3(0.0f, 0.0f, 1.0f),
			Vec3(1.0f, 0.0f, 1.0f),
			Vec3(1.0f, 1.0f, 1.0f),
			Vec3(0.0f, 1.0f, 0.0f),
			Vec3(0.0f, 0.0f, 0.0f),
			Vec3(1.0f, 0.0f, 0.0f),
			Vec3(1.0f, 1.0f, 0.0f),
		};

		static const int msTriangles[12][3]=
		{
			{0, 1, 2}, {0, 2, 3},
			{0, 4, 5}, {0, 5, 1},
			{1, 5, 6}, {1, 6, 2},
			{2, 6, 7}, {2, 7, 3},
			{3, 7, 4}, {3, 4, 0},
			{5, 4, 7}, {5, 7, 6},
		};

		static const Vec3 msNormals[12]=
		{
			Vec3(0.0f, 0.0f, 1.0f),
			Vec3(0.0f, 0.0f, 1.0f),
			Vec3(-1.0f, 0.0f, 0.0f),
			Vec3(-1.0f, 0.0f, 0.0f),
			Vec3(0.0f, -1.0f, 0.0f),
			Vec3(0.0f, -1.0f, 0.0f),
			Vec3(1.0f, 0.0f, 0.0f),
			Vec3(1.0f, 0.0f, 0.0f),
			Vec3(0.0f, 1.0f, 0.0f),
			Vec3(0.0f, 1.0f, 0.0f),
			Vec3(0.0f, 0.0f, -1.0f),
			Vec3(0.0f, 0.0f, -1.0f),
		};

		if(level==maxLevel)
		{
			const float s=2.0f/(float)l;
			Vec3 o(xi*s-1, yi*s-1, zi*s-1);
			for(int i=0;i<12;i++)
			{
				if(m&(1<<(i/2)))
				{
					Vec3 verts[3];
					Vec3::Scale(verts[0], msVertices[msTriangles[i][0]], s);
					verts[0] += o;
					Vec3::Scale(verts[1], msVertices[msTriangles[i][1]], s);
					verts[1] += o;
					Vec3::Scale(verts[2], msVertices[msTriangles[i][2]], s);
					verts[2] += o;
					Vec3::Scale(verts[0], verts[0], 1.0f);
					Vec3::Scale(verts[1], verts[1], 1.0f);
					Vec3::Scale(verts[2], verts[2], 1.0f);
					mdl->Vertices.Add(verts[0]);
					mdl->Vertices.Add(verts[1]);
					mdl->Vertices.Add(verts[2]);
					ObjFace face;
					face.VertexIds[0] = mdl->Vertices.Count()-3;
					face.VertexIds[1] = mdl->Vertices.Count()-2;
					face.VertexIds[2] = mdl->Vertices.Count()-1;
					face.SmoothGroup = 0;
					face.NormalIds[0] = -1; face.NormalIds[1] = -1; face.NormalIds[2] = -1;
					face.TexCoordIds[0] = -1; face.TexCoordIds[1] = -1; face.TexCoordIds[2] = -1;
					mdl->Faces.Add(face);
				}
			}
		}
		else
		{
			for(int i=0;i<=2;i++)for(int j=0;j<=2;j++)for(int k=0;k<=2;k++)
			{
				if((i&1)+(j&1)+(k&1)<=1)
				{
					int m2=0;

					if(i==0)m2|=(1<<1)&m;
					if(i==2)m2|=(1<<3)&m;
					if(j==0)m2|=(1<<2)&m;
					if(j==2)m2|=(1<<4)&m;
					if(k==0)m2|=(1<<5)&m;
					if(k==2)m2|=(1<<0)&m;
					
					if(i==2&&(j==1 || k==1))m2|=(1<<1);
					if(i==0&&(j==1 || k==1))m2|=(1<<3);
					if(j==2&&(k==1 || i==1))m2|=(1<<2);
					if(j==0&&(k==1 || i==1))m2|=(1<<4);
					if(k==2&&(i==1 || j==1))m2|=(1<<5);
					if(k==0&&(i==1 || j==1))m2|=(1<<0);

					CreateMengerSponge(xi*3+i, yi*3+j, zi*3+k, l*3, m2, level+1, maxLevel, mdl);
				}
			}
		}
	}
}
